import 'dart:typed_data';

import 'input_buffer.dart';

class OutputBuffer {
  int length;
  final bool bigEndian;

  /// Create a byte buffer for writing.
  OutputBuffer({int size = _BLOCK_SIZE, this.bigEndian = false})
      : _buffer = Uint8List(size ?? _BLOCK_SIZE),
        length = 0;

  void rewind() {
    length = 0;
  }

  /// Get the resulting bytes from the buffer.
  List<int> getBytes() {
    return Uint8List.view(_buffer.buffer, 0, length);
  }

  /// Clear the buffer.
  void clear() {
    _buffer = Uint8List(_BLOCK_SIZE);
    length = 0;
  }

  /// Write a byte to the end of the buffer.
  void writeByte(int value) {
    if (length == _buffer.length) {
      _expandBuffer();
    }
    _buffer[length++] = value & 0xff;
  }

  /// Write a set of bytes to the end of the buffer.
  void writeBytes(List<int> bytes, [int len]) {
    len ??= bytes.length;
    while (length + len > _buffer.length) {
      _expandBuffer((length + len) - _buffer.length);
    }
    _buffer.setRange(length, length + len, bytes);
    length += len;
  }

  void writeBuffer(InputBuffer bytes) {
    while (length + bytes.length > _buffer.length) {
      _expandBuffer((length + bytes.length) - _buffer.length);
    }
    _buffer.setRange(length, length + bytes.length, bytes.buffer, bytes.offset);
    length += bytes.length;
  }

  /// Write a 16-bit word to the end of the buffer.
  void writeUint16(int value) {
    if (bigEndian) {
      writeByte((value >> 8) & 0xff);
      writeByte(value & 0xff);
      return;
    }
    writeByte(value & 0xff);
    writeByte((value >> 8) & 0xff);
  }

  /// Write a 32-bit word to the end of the buffer.
  void writeUint32(int value) {
    if (bigEndian) {
      writeByte((value >> 24) & 0xff);
      writeByte((value >> 16) & 0xff);
      writeByte((value >> 8) & 0xff);
      writeByte((value) & 0xff);
      return;
    }
    writeByte((value) & 0xff);
    writeByte((value >> 8) & 0xff);
    writeByte((value >> 16) & 0xff);
    writeByte((value >> 24) & 0xff);
  }

  /// Return the subset of the buffer in the range [start:end].
  /// If [start] or [end] are < 0 then it is relative to the end of the buffer.
  /// If [end] is not specified (or null), then it is the end of the buffer.
  /// This is equivalent to the python list range operator.
  List<int> subset(int start, [int end]) {
    if (start < 0) {
      start = (length) + start;
    }

    if (end == null) {
      end = length;
    } else if (end < 0) {
      end = length + end;
    }

    return Uint8List.view(_buffer.buffer, start, end - start);
  }

  /// Grow the buffer to accommodate additional data.
  void _expandBuffer([int required]) {
    final blockSize = (required != null)
        ? required
        : (_buffer.isEmpty)
            ? _BLOCK_SIZE
            : (_buffer.length * 2);
    final newBuffer = Uint8List(_buffer.length + blockSize);
    newBuffer.setRange(0, _buffer.length, _buffer);
    _buffer = newBuffer;
  }

  static const _BLOCK_SIZE = 0x2000; // 8k block-size
  Uint8List _buffer;
}
